#include "client.h"
#include "client_util.h"
// 日志文件名

static pthread_mutex_t log_mutex = PTHREAD_MUTEX_INITIALIZER;


// 假设max_tokens是数组tokens的最大大小
// 在使用后，记得要释放空间
void splitString(const char * pstrs, char *tokens[], int max_tokens, int * pcount) {
    int token_count = 0;
    char *token = strtok((char *)pstrs, " "); // 使用空格作为分隔符

    while (token != NULL && token_count < max_tokens - 1) { // 保留一个位置给NULL终止符
        char * pstr = (char*)calloc(1, strlen(token) + 1);
        strcpy(pstr, token);
        tokens[token_count] = pstr;//保存申请的堆空间首地址
        token_count++;
        token = strtok(NULL, " "); // 继续获取下一个token
    }
    // 添加NULL终止符
    tokens[token_count] = NULL;
    *pcount= token_count;
}

void freeStrs(char * pstrs[], int count)
{
    for(int i = 0; i < count; ++i) {
        free(pstrs[i]);
    }
}

int getCommandType(const char* str) {
    if (!strcmp(str, "pwd")) 
        return CMD_TYPE_PWD;
    else if (!strcmp(str, "ls"))
        return CMD_TYPE_LS;
    else if (!strcmp(str, "cd"))
        return CMD_TYPE_CD;
    else if (!strcmp(str, "mkdir"))
        return CMD_TYPE_MKDIR;
    else if (!strcmp(str, "rm"))
        return CMD_TYPE_RMDIR;
    else if (!strcmp(str, "puts")){
        return CMD_TYPE_PUTS;
    }
    else if (!strcmp(str, "gets")){
        return CMD_TYPE_GETS;
    }
    else{
        return CMD_TYPE_NOTCMD;
    }
}

//解析命令
int parseCommand(const char* pinput, int len, train_t* pt) {
    char* pstrs[10] = { 0 };
    int cnt = 0;
    splitString(pinput, pstrs, 10, &cnt);
    pt->type = (CmdType)getCommandType(pstrs[0]);
    //暂时限定命令行格式为：
    //1. cmd
    //2. cmd content
    if (cnt > 1) {
        pt->len = strlen(pstrs[1]);
        strncpy(pt->buff, pstrs[1], pt->len);
    }
    return 0;
}

//作用：确定接收len字节的数据
int recvn(int sockfd, void* buff, int len) {
    int left = len;//还剩下多少个字节需要接收
    char* pbuf = (char*)buff;
    int ret = -1;
    while(left > 0) {
        ret = recv(sockfd, pbuf, left, 0);
        if (ret == 0) {
            break;
        }
        else if(ret < 0) {
            perror("recv");
            return -1;
        }
        left -= ret;
        pbuf += ret;
    }
    //当退出while循环时，left的值等于0
    return len - left;
}





//作用: 确定发送len字节的数据
int sendn(int sockfd, const void* buff, int len)
{
    int left = len;
    const char* pbuf = (char*)buff;
    int ret = -1;
    while(left > 0) {
        ret = send(sockfd, pbuf, left, 0);
        if(ret < 0) {
            perror("send");
            return -1;
        }

        left -= ret;
        pbuf += ret;
    }
    return len - left;
}

//使用了 EVP 接口来计算文件的 MD5 值。请确保在 Makefile 中正确链接 OpenSSL库
//不用看懂直接用
void get_file_md5(const char* filename, unsigned char* md5) {
    FILE *file = fopen(filename, "rb");
    if (!file) {
        perror("Error opening file");
        exit(EXIT_FAILURE);
    }

    EVP_MD_CTX *context = EVP_MD_CTX_new();
    if (context == NULL) {
        perror("Error creating context");
        fclose(file);
        exit(EXIT_FAILURE);
    }

    if (EVP_DigestInit_ex(context, EVP_md5(), NULL) != 1) {
        perror("Error initializing digest");
        EVP_MD_CTX_free(context);
        fclose(file);
        exit(EXIT_FAILURE);
    }

    unsigned char buffer[1024];
    size_t bytesRead;
    while ((bytesRead = fread(buffer, 1, sizeof(buffer), file)) > 0) {
        if (EVP_DigestUpdate(context, buffer, bytesRead) != 1) {
            perror("Error updating digest");
            EVP_MD_CTX_free(context);
            fclose(file);
            exit(EXIT_FAILURE);
        }
    }

    if (EVP_DigestFinal_ex(context, md5, NULL) != 1) {
        perror("Error finalizing digest");
        EVP_MD_CTX_free(context);
        fclose(file);
        exit(EXIT_FAILURE);
    }

    EVP_MD_CTX_free(context);
    fclose(file);
}

// 获取当前时间的字符串表示
void get_current_time(char* buffer, size_t size) {
    time_t now = time(NULL);
    struct tm *t = localtime(&now);
    strftime(buffer, size, "%Y-%m-%d %H:%M:%S", t);
}

// 记录日志的函数
//by 2024.6.17 sjk
void WriteLog(const char *format, ...) 
{
    pthread_mutex_lock (&log_mutex); 
    FILE *logfile = fopen(LOG_FILE, "a");
    if (logfile == NULL) {
        perror("打开日志文件失败");
        return;
    }

    // 获取当前时间
    char time_str[20];
    get_current_time(time_str, sizeof(time_str));

    // 获取进程 ID 和线程 ID
    pid_t pid = getpid();
    pthread_t tid = pthread_self();

    // 写入时间、进程 ID 和线程 ID
   
    fprintf(logfile, "[%s] pid: %d, tid: %lu, ", time_str, pid, (unsigned long)tid);

    // 写入自定义的日志信息
    va_list args;
    va_start(args, format);
    vfprintf(logfile, format, args);
    va_end(args);
    // 换行符
    fprintf(logfile, "\n");
    //客户端日志只输出在日志文件不打印在控制台,不然看起来很乱
   // printf("[%s] pid: %d, tid: %lu,",time_str, pid, (unsigned long)tid);
    //va_list args;
    va_start(args, format);
    vprintf(format, args);
    va_end(args);
    printf("\n");

//
    fclose(logfile);
    pthread_mutex_unlock (&log_mutex); 
}

/* 
    * 函数名：         GetIniKeyString 
    * 入口参数：        title 
    *                      配置文件中一组数据的标识 
    *                  key 
    *                      这组数据中要读出的值的标识 
    *                  filename 
    *                      要读取的文件路径 
    * 返回值：         找到需要查的值则返回正确结果 0 
    *                  否则返回-1 
    */ 



int GetIniKeyString(char *section, char *key, char *filename, char *buf)
{
    FILE *fp;
 
    // 用来标记是否找到section
    int flag = 0;
    char sSection[64], *wTmp;
    char sLine[1024];
 
    // 节section字符串
    sprintf(sSection, "[%s]", section);
 
    if (NULL == (fp = fopen(filename, "r")))
    {
        printf("open %s failed.\n", filename);
        return -1;
    }
 
    // 读取ini中的每一行
    while (NULL != fgets(sLine, 1024, fp))
    {
        // 处理ini文件中的注释行
        if ('#' == sLine[0])
            continue;
 
        if (';' == sLine[0])
            continue;
 
        // 定位=的位置
        wTmp = strchr(sLine, '=');
        if ((NULL != wTmp) && (1 == flag))
        {
            if (0 == strncmp(key, sLine, strlen(key)))
            {
                sLine[strlen(sLine) - 1] = '\0';
 
                while (*(wTmp + 1) == ' ')
                {
                    wTmp++;
                }
 
                // 获取key对应的value
                strcpy(buf, wTmp + 1);
 
                fclose(fp);
                return 0;
            }
        }
        else
        {
            if (0 == strncmp(sSection, sLine, strlen(sSection)))
            {
                // 不存在键值对的情况下，标记flag
                flag = 1;
            }
        }
    }
 
    fclose(fp);
    return -1;
}
 
 
/*
* 参数：
* section:  配置文件中的节sectin
* key:      配置项的标识
* val:      配置项标识对应的值
* filename: ini配置文件路径
*
* 返回值：    成功返回结果0，否则返回-1
*/
int PutIniKeyString(char *section, char *key, char *val, char *filename)
{
    FILE *fpr, *fpw;
    int flag = 0;
    int ret;
    char sLine[1024], sSection[32], *wTmp;
 
    sprintf(sSection, "[%s]", section);
 
    if (NULL == (fpr = fopen(filename, "r")))
        return -1;
 
    // 临时文件名
    sprintf(sLine, "%s.tmp", filename);
 
    fpw = fopen(sLine, "w+");
    if (NULL == fpw)
        return -1;
 
    while (NULL != fgets(sLine, 1024, fpr))
    {
        if (2 != flag)
        {
            wTmp = strchr(sLine, '=');
            if ((NULL != wTmp) && (1 == flag))
            {
                if (0 == strncmp(key, sLine, strlen(key)))
                {
                    // 找到对应的key
                    flag = 2;
                    sprintf(wTmp + 1, " %s\n", val);
                }
            }
            else
            {
                if (0 == strncmp(sSection, sLine, strlen(sSection)))
                {
                    // 找到section的位置
                    flag = 1;
                }
            }
        }
 
        // 写入临时文件
        fputs(sLine, fpw);
    }
 
    fclose(fpr);
    fclose(fpw);
 
    sprintf(sLine, "%s.tmp", filename);
 
    // rename函数在windows上和linux上表现有差异，看文章中的备注
    ret = rename(sLine, filename);
    if (ret != 0)
    {
        if (errno == EEXIST)
        {
            // 如果目标文件已经存在，需要先删除，再重命名
            if (remove(filename) == 0)
            {
                if (rename(sLine, filename) == 0)
                {
                    // printf("File %s has been renamed to %s\n", sLine, filename);
                    return 0;
                }
            }
        }
    }
 
    return ret;
}
 // 定义ANSI颜色码
 #define RESET   "\033[0m"
 #define RED     "\033[31m"
 #define GREEN   "\033[32m"
 #define YELLOW  "\033[33m"
 #define BLUE    "\033[34m"
 #define MAGENTA "\033[35m"
 #define CYAN    "\033[36m"
 #define WHITE   "\033[37m"

 void currinfo_printf(curr_info_t curr) {
     printf(GREEN "%s@%s" MAGENTA ":/%s$ " RESET, curr.user_name, curr.user_name, curr.pwd);

     fflush(stdout);
 }

